iT邦幫忙

2025 iThome 鐵人賽

DAY 2
0
Modern Web

Line Bot × NestJS:30 天開發日記系列 第 2

Day 2:從 webhook 到後端,打造你的專屬機器人

  • 分享至 

  • xImage
  •  

2025 鐵人賽背景圖

前言

上一篇,帶大家透過「Chat 模式」,讓自身變成客服機器人的方式。這篇會從「Webhook 模式」切入,並以 NestJS 作為後端框架,教你如何在休息時間,也能讓後端伺服器當你的替身幫你解惑使用者的問題。

本文將著重於實現以下核心功能:

  • 使用後端伺服器接收 LINE 伺服器推送的事件
  • 根據 Line 伺服器訊息事件自動回覆使用者「Hello World」

本日程式碼的範例連結

Step 1:啟用 LINE Official Account webhook 功能

預設 webhook 功能是關閉,需要設置完 webhook URL 才可以啟用這個功能

LINE Official Account webhook 設定

這邊會讓你選擇 Provider,這邊我選擇使用 Day1 創建的Antonio(2025 IT 鐵人賽專用)

LINE Official Account Messaging API Provider 選擇

Step 2:設定你的 webhook URL 連結

Webhook URL 用於建立後端伺服器與 Line 平台的連結關係

完成 webhook Provider 設定後,如果回到 LINE Response Setting 頁面,你會發現原本說明已經改變,這表示已經進入到 webhook 設定的最後一個步驟:設置 webhook URL。

接下來的所有設定操作都會改在 LINE Developers 平台進行。雖然 webhook URL 在 LINE Developers 和 LINE Official Account Manager 兩個平台都可以設定,但我們選擇在 LINE Developers 進行的主要原因是在 Line developers 有驗證連線的按鈕可以確認連線是否成功。

LINE Official Account 啟用成功畫面

LINE Official Account Response settings 說明改變

Line Official Account webhook 選項文字


Webhook 與 LineBot 之間的關係

首先,這邊會帶著大家理解 Line 平台與後端伺服器的關係,後端伺服器就像是一個 24hr 不休息的員工,我們可以讓他跟 Line 平台連接,藉此達到即使不在電腦前面,當收到 Line 官方帳號的相關事件(新朋友加入、封鎖、傳送訊息)伺服器也能代替我們處理。

不僅如此,伺服器能夠按照我們所想的邏輯或是搭配 AI 回覆,讓回覆使用者的內容不要那麼像是罐頭訊息,也可以根據不同的使用情境(例如:聖誕節)動態調整回覆訊息的風格。

以下是 Line 平台跟後端伺服器搭配運作的關係圖:

LINE 官方帳號訊息處理採用「事件驅動」的架構,透過 Webhook 機制實現即時的雙向溝通。
(白話解釋: Webhook URL 就像是你的官方帳號頻道與 LINE 平台之間的專屬橋樑。當有使用者與官方帳號互動時,LINE 平台會主動透過這座橋樑將訊息推送到你的伺服器進行處理,而不需要你的伺服器不斷詢問「有沒有新訊息?」

Line 平台與後端伺服器傳送訊息流程圖

LINE 傳送訊息流程解釋:

  1. 用戶發送訊息:使用者透過 LINE 應用程式發送一則訊息。
  2. LINE 平台事件轉發:當使用者與 LINE 官方帳號互動時,LINE 平台會根據綁定的 Channel ID(頻道識別碼),將事件資料以 HTTP POST 方式傳送至頻道設定的 Webhook URL。伺服器接收到這些事件後,可以進行相對應的處理與回應。
  3. 伺服器回應處理:當伺服器完成訊息處理後,透過 LINE Messaging API 將回應訊息傳送回 LINE 平台,最終傳達給使用者。

動手來做一個後端伺服器吧!

前期會搭配使用 ngrok 的方式,方便大家快速看到 Demo 成效

首先,透過 NestJS CLI 快速建立後端的環境,讓大家快速體會使用 webhook 與 LineBot 交互的感覺。

使用的環境配置如下:

  • nodejs 版本:v20.18.1
  • nvm 版本:0.39.1

Step 2-1:輸入指令安裝 NestCli

選擇你喜歡的套件管理器:如果不知道用哪一個推薦使用pnpm

pnpm add -g @nestjs/cli

Step 2-2:輸入指令安裝 NestCli

nest new linebot-webhook-server // 創建一個 nest專案,名稱為 linebot-webhook-server

看到終端機寫著Successfully created代表已經創立好專案囉!
nest cli 完成畫面

Step 2-3:按照 nest cli 的指示執行以下兩個指令

cd linebot-webhook-server // 進入這個創建完的資料夾中
pnpm run start:dev // 啟動這個 nest 專案(支援熱重載功能)

看到終端機寫著Nest application successfully started代表伺服器成功啟動!
開啟 NestJS 專案伺服器

預設伺服器啟動的網址是:http://localhost:3000
(看到 hello world 就代表順利透過後端伺服器取回預設回傳值)

Step 2-4:安裝 line sdk(Line Bot 相關套件)

記得先關掉伺服器(control + c),再繼續安裝

pnpm install @line/bot-sdk --save

Step 2-5:安裝處理環境變數的套件

白話解釋:私人秘密不想被別人看到的檔案

pnpm install @nestjs/config

Step 2-6:創建一個檔案命名.env並且在其中貼上以下兩行

  • LINE_CHANNEL_SECRET:驗證來問候後端伺服器的是 Line Platform
  • LINE_CHANNEL_ACCESS_TOKEN:驗證你是頻道授權者,可以透過 Bot 與使用者互動
LINE_CHANNEL_ACCESS_TOKEN=你的 Line Bot 頻道存取權杖
LINE_CHANNEL_SECRET=你的 Line Bot 頻道密鑰

1️⃣ LINE_CHANNEL_SECRET 放在 Basic settings 頁籤:
LINE_CHANNEL_SECRET

2️⃣ LINE_CHANNEL_ACCESS_TOKEN 放在 Messaging API 頁籤:
LINE_CHANNEL_ACCESS_TOKEN

Step 2-7:極簡化伺服器資料夾結構 + 註冊環境變數

將檔案結構調整成最簡單能運行的方式,在逐漸優化!

  • 刪除app.controller.specapp.service的部分
  • 創建 config 資料夾及 line.config.ts 檔案

初始化檔案結構

line.config.ts 內容

import { ClientConfig } from '@line/bot-sdk';
import { ConfigService } from '@nestjs/config';

// 註冊名稱
export const LINE_CONFIG = 'LINE_CONFIG';

// 註冊的常數(這邊讀取的是.env 環境變數裡面的內容)
const lineConfig = (configService: ConfigService): ClientConfig => ({
  channelAccessToken:
    configService.get<string>('LINE_CHANNEL_ACCESS_TOKEN') ?? '',
  channelSecret: configService.get<string>('LINE_CHANNEL_SECRET') ?? '',
});

// 匯出成 NestJS Provider 供依賴注入系統使用(讓 NestJS 可以透過 DI 容器管理此設定)
export const LineConfigProvider = {
  provide: LINE_CONFIG,
  useFactory: (configService: ConfigService) => lineConfig(configService),
  inject: [ConfigService],
};

Step 2-8:將 lineConfig Provider 註冊到根模組中使用

  • ConfigModule.forRoot({ isGlobal: true }):全域註冊環境變數,讓整個應用程式都能存取環境變數
  • LineConfigProvider 註冊:將 LineConfigProvider 加入到 providers 陣列中,讓它可以被依賴注入系統使用。

src/app.module.ts:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ConfigModule } from '@nestjs/config';
import { LineConfigProvider } from '../config/line.config';

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true })],
  controllers: [AppController],
  providers: [LineConfigProvider],
})
export class AppModule {}

Step 2-9:設定 /webhook 路徑接收 Line 平台傳遞的事件

這裡設定的路由前綴為 /webhook,這是專門用來接收 Line Bot webhook 事件的端點路徑。當 Line 平台有訊息或事件需要推送給我們的應用程式時,會透過這個特定的路由路徑進行回調通知

src/app.controller.ts:

import { Body, Controller, Inject, Post } from '@nestjs/common';
import {
  ClientConfig,
  WebhookRequestBody,
  messagingApi,
  MessageEvent,
  WebhookEvent,
} from '@line/bot-sdk';
import { LINE_CONFIG } from '../config/line.config';

@Controller()
export class AppController {
  private readonly lineClient: messagingApi.MessagingApiClient;

  // 根據配置檔案初始化 LINE Messaging API 客戶端
  constructor(@Inject(LINE_CONFIG) private readonly lineConfig: ClientConfig) {
    this.lineClient = new messagingApi.MessagingApiClient({
      channelAccessToken: this.lineConfig.channelAccessToken,
    });
  }

  @Post('/webhook')
  handleWebhook(@Body() body: WebhookRequestBody): Promise<string> {
    console.log('Webhook 從 line platform 接收到的資訊:', body);
    return 'Webhook processed successfully';
  }
}

Step 2-10:Nest 後端伺服器開啟的狀態下搭配使用 ngrok

Line Webhook URL 必須是 HTTPS 網址,所以這邊需要搭配 ngrok 使用

ngrok是一個反向代理工具,能夠將本地端口轉發至公開的 HTTPS 網址。它提供第三方代理服務,讓外部用戶可以直接透過公開網址存取您本地運行的應用程式

使用指令非常簡單,僅需要打開終端機輸入以下指令:

ngrok http 3000

👇 看到以下顯示的樣子就代表已經成功建立公開網址
ngrok 運行畫面

這個https://d120-49-213-168-27.ngrok-free.app就是 webhook URL

這種方式屬於臨時伺服器的方式,連結有時候會斷開。如果重新開啟 ngrok 需要記得 https 連結位置會改變,不要忘記連帶改動設定的 webhook URL

Step 3:在 Line developers 設置 webhook URL 並測試

登入 Line developers 後,在 Message API Webhook settings可以設定 Webhook URL:
在 Line developers 設置 Webhook URL

特別注意要加上 /webhook 的路徑前綴,這是我們後端設置接收 Line 事件的端點,這樣才可以正確打到架設的後端伺服器喔!

按下Verify 按鈕的同時,Line 平台就會發送事件到你的後端伺服器,這時候只要後端伺服器回傳的請求狀態碼是 200,畫面上會顯示 Success。這時候就代表已經成功建立 Line 平台與你架設的後端伺服器之間的連結。

Step 4:在 Line developers 打開 Use webhook 按鈕

在剛才設置 Webhook URL 的下方,找到並開啟「Use webhook」開關。此設定會與先前在 Line Official Account Manager 中的配置進行連動。


開啟後,你可以試著在官方帳號輸入訊息,查看終端機印出的訊息:

後端伺服器終端機印出 Line 平台送出的事件

印出收到的訊息,代表你已經能成功接收到使用者跟你的 Line 官方帳號互動的事件囉!

Step 5:修改 app.controller.ts 回覆使用者 Hello World!

LineBot 與使用者的互動包含多種事件類型,這邊舉例三種最常見的事件:

  • Follow event:用戶加入 Line 官方帳號
  • Unfollow event:用戶封鎖 Line 官方帳號
  • Message event:用戶傳送訊息

本系列的文章將聚焦於最常見的使用情境 Message event

在下面程式碼中,我們會特別展示一個重要概念:每則訊息都帶有專屬的 Reply Token(可以理解為該訊息的「身分證」)。後端伺服器可以利用這個 Token 來回應特定的用戶訊息,這就是 Line Bot 回覆訊息的第一種方式 Reply Message

src/app.controller.ts:

//...略

// 限制型別先收斂成部分事件,搭配 eventHandler 使用
type HandlerMap = {
  [K in WebhookEvent['type']]: (
    event: Extract<WebhookEvent, { type: K }>,
  ) => Promise<void>;
};

@Controller()
export class AppController {
  private readonly lineClient: messagingApi.MessagingApiClient;

  // 根據配置檔案初始化 LINE Messaging API 客戶端
  constructor(@Inject(LINE_CONFIG) private readonly lineConfig: ClientConfig) {
    this.lineClient = new messagingApi.MessagingApiClient({
      channelAccessToken: this.lineConfig.channelAccessToken,
    });
  }

  /**
   * Webhook 端點處理器
   * 接收來自 LINE Platform 的事件通知
   * @param body LINE Platform 傳送的 Webhook 請求本體
   * @returns 處理完成的回應訊息
   */
  @Post('/webhook')
  async handleWebhook(@Body() body: WebhookRequestBody): Promise<string> {
    console.log('Webhook 從 line platform 接收到的資訊:', body);
    const { events } = body;

    // 事件處理器映射表
    const eventHandler = {
      message: (event) => this.handleMessageEvent(event),
    } satisfies Partial<HandlerMap>;

    // 逐項處理每一個事件
    for (const event of events) {
      const { type } = event;
      if (type === 'message') {
        await eventHandler[type](event);
      }
    }

    return 'Webhook processed successfully';
  }

  /**
   * 處理訊息事件
   * @param event 訊息事件物件,包含用戶訊息內容(text)和回覆憑證(replyToken)
   */
  private async handleMessageEvent(event: MessageEvent): Promise<void> {
    const { replyToken } = event;
    console.log('收到訊息事件', event);
    console.log('訊息憑證(身分證):', replyToken);
    await this.lineClient.replyMessage({
      replyToken,
      messages: [
        {
          type: 'text',
          text: 'hello world',
        },
      ],
    });
  }
}

輸入完訊息,就會看到你的專屬代理人回覆 hello world!的訊息 ⭐⭐⭐

這邊眼尖的你可能會發現我這邊沒使用到 LINE_CHANNEL_SECRET 也可以使用 Line Bot webhook 的功能,那為什麼還需要這個呢?明天的文章帶你揭曉

後端伺服器傳遞 Hello world 成功圖片


上一篇
Day 1:Hello World LineBot
系列文
Line Bot × NestJS:30 天開發日記2
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言